#pragma once

#include "LocatableObject.h"
#include "Attribute.h"

class Spell;
class AuraObject;

class Unit : public LocatableObject, public Attribute::Loader
{
public:
	Unit(OBJECT_TYPE objType);
	virtual ~Unit();

	bool Init(uint32 entry);
	virtual bool SubInit();

	virtual void Update(uint64 diffTime);
	virtual void SubUpdate(uint64 diffTime);

	virtual void OnDelete();

	virtual const std::string& GetName() const;

	virtual void BuildCreatePacketForPlayer(INetPacket& pck, Player* pPlayer);

	float GetCombatBestDist(Unit* pEnemy) const;

	uint32 GetEntry() const { return m_pProto->charTypeId; }
	const CharPrototype* GetProto() const { return m_pProto; }

protected:
	const CharPrototype* m_pProto;

public:
	Player* GetPlayerOwner();

public:
	void FastRevive();
	void ReloadAttributes();
	void ReloadPartAttributes(uint32 flags);
	Attribute& GetAttribute() { return m_attribute; }
	u32 GetLevel() const { return GetS32Value(UNIT_FIELD_LEVEL); }
protected:
	void ReloadHPMPValue();
	virtual void OnRevive();
	Attribute m_attribute;
	s64 m_hpMaxReachable;
	s64 m_mpMaxReachable;

protected:
	bool RecoveryHPValue(uint64 diffTime);
	bool RecoveryMPValue(uint64 diffTime);
	bool m_isRecoveryHPDisable;

public:
	void ForceMotionless();
	float GetMoveSpeed() const;
	float GetTurnSpeed() const;
	void ModMoveSpeedInc(float inc);
	void ModTurnSpeedInc(float inc);
	void SetMoveMode(MoveMode moveMode);
	MoveMode GetMoveMode() const { return m_moveMode; }
protected:
	MoveMode m_moveMode;

public:
	virtual void OnChangePriorZone(const MapZone* pOldZone);
	virtual void OnChangePosition();
	uint32 GetPriorZoneFlags() const;
protected:
	const MapZone* FilterPriorZone() const;
	const MapZone* m_pPriorZone;

public:
	virtual bool IsFriend(const Unit* pTarget) const;
	virtual bool IsHostile(const Unit* pTarget) const;
	bool CanAttack(Unit* pTarget);
	void SetTeamSide(ArenaTeamSide teamSide);
	ArenaTeamSide GetTeamSide() const;

public:
	void AttachOverlayAuraState(AURA_STATE auraState);
	void DetachOverlayAuraState(AURA_STATE auraState);
	void AddSysAuraState(AURA_STATE auraState);
	void RemoveSysAuraState(AURA_STATE auraState);
	bool IsOutOfControl() const;
	bool IsOutOfMove() const;
protected:
	void OnNewAuraState();
	void OnOutOfControl();
	void OnOutOfMove();
	struct AuraInfo {
		size_t auraNum = 0;
		bool hasSysAura = false;
	};
	AuraInfo m_auraInfos[AURA_STATE_COUNT];

public:
	void Strike(Unit* pVictim, LuaTable t);
	uint64 Hurt(double hpValue, Unit* pHurter);
	uint64 LoseHP(double hpValue, Unit* pHurter);
	uint64 TreatHP(double hpValue, Unit* pTreater);
	void AddHPValue(uint64 hpValue);
	void SubHPValue(uint64 hpValue);
	void AddMPValue(uint64 mpValue);
	void SubMPValue(uint64 mpValue);
	double GetHPRate() const;
	double GetMPRate() const;
	bool IsDead() const { return m_isDead; }
protected:
	bool CanLostHP() const;
	bool CanKilled() const;
	virtual void OnLoseHP(Unit* pHurter, uint64 hurtValue);
	virtual void OnKilled(Unit* pKiller);
	virtual void OnDead(Unit* pKiller);
	std::function<void(uint64)> m_strikeHook;
	std::function<void(uint64)> m_hurtHook;
	bool m_isDead;

public:
	virtual void SetTarget(Unit* pTarget);
	void AddEnemy(Unit* enemy);
	void AddSenseEnemy(Unit* enemy);
	void RemoveEnemy(ObjGUID enemyGuid);
	bool HasEnemy(ObjGUID enemyGuid) const;
	bool HasTarget() const { return m_pTarget != NULL; }
	Unit* GetTarget() const { return m_pTarget; }
	ObjGUID GetTargetGuid() const {
		return m_pTarget != NULL ? m_pTarget->GetGuid() : ObjGUID_NULL;
	}
protected:
	void ClearEnemyList();
	void CleanEnemySenseList();
	std::unordered_map<ObjGUID, EnemyInfo> m_enemyList;
	std::unordered_set<ObjGUID> m_enemySenseList;
	time_t m_lastCombatTime;
	Unit* m_pTarget;

public:
	void ResetToIdle();
	bool TryLockAttackObject();
	void SetInCombat(bool isInCombat = true);
	bool IsInCombat() const { return m_isInCombat; }
protected:
	bool SearchEnemyInSight();
	virtual void OnResetToIdle();
	virtual void OnChangeCombatStatus();
	bool m_isInCombat;

public:
	vector3f GetEnemySlotDir(Unit* enemy);
protected:
	std::pair<vector3f, bool> TryFastGetEnemySlotDir(Unit* enemy);
	std::vector<Unit*> CleanEnemySlot(Unit* enemy);
	void RelocateAllEnemySlot(const std::vector<Unit*>& enemyList);
	void RelocateEnemySlot(const std::vector<Unit*>& enemyList, Unit* enemy);
	std::vector<EnemySlot> m_enemySlotList;

public:
	void MoveToPriorPosition();
	void MoveToCombatPosition(float dozeDist = .0f, float dullDist = .0f);
	void MoveToTarget(Unit* pTarget,
		float dozeDist = .0f, float dullDist = .0f, float keepDist = .0f,
		const vector3f& facePos = vector3f_INVALID);
	void MoveToPosition(const vector3f& tgtPos,
		float dozeDist = .0f, float dullDist = .0f, float keepDist = .0f,
		const vector3f& facePos = vector3f_INVALID);
public:
	void CancelPlanMove();
	void StopMove();
	void SetPriorMove(Unit* pTarget);
	void SetKeepDist(float keepDist);
	void SetFacePos(const vector3f& facePos);
	bool IsMoving() const { return m_isMoving; }
	bool IsPriorMove() const { return m_isPriorMove && m_isMoving; }
	bool IsFacePos() const { return m_facePos.y < INT_MAX; }
protected:
	void StartDullMove(float keepDist, const vector3f& facePos);
	void StartMove(float keepDist, const vector3f& facePos, const vector3f& tgtPos);
	void SetMovePath(const vector3f fMovePath[], int iMovePathNum);
	int64 UpdateMove(int64 availTime);
	void BuildCreateUpdateMoveBlock(INetPacket& pck) const;
	ObjGUID m_priorGuid;
	float m_dozeDist;
	float m_dullDist;
	vector3f m_facePos;
	vector3f m_tgtPos;
	float m_keepDist;
	std::vector<vector3f> m_fMovePath;
	uint32 m_iMovePathCount;
	bool m_isMoving;
	bool m_isPlanMoving;
	bool m_isPriorMove;

public:
	void StartTurn(const vector3f& tgtDir);
	void StopTurn();
	bool IsTurning() const { return m_isTurning; }
protected:
	int64 UpdateTurn(int64 availTime);
	void BuildCreateUpdateTurnBlock(INetPacket& pck) const;
	vector3f m_tgtDir;
	bool m_isTurning;

public:
	virtual bool LearnSpell(uint32 spellID, uint32 spellLevel, uint32 flags);
	virtual bool ForgetSpell(uint32 spellID);
	bool IsSpellLearned(uint32 spellID) const;
	uint32 GetSpellLevel(uint32 spellID) const;
protected:
	std::unordered_map<uint32, SpellPropInfo> m_spellPropList;

public:
	void PushPassiveSpell(uint32 spellID, uint32 spellLevel);
	void PopPassiveSpell(uint32 spellID, uint32 spellLevel);
	void UpdatePassiveSpellStatusByNone();
	void TriggerPassiveSpellEventByHit();
	void TriggerPassiveSpellEventByHitBy();
protected:
	class PassiveSpellStatusHelper;
	struct PassiveSpellInfo {
		const SpellPrototype* pSpellProto;
		std::map<uint32, size_t> spellLevels;
	};
	struct PassiveSpellStatus {
		const SpellPrototype* pSpellProto;
		uint32 spellLevel;
		bool isEnable;
		bool isNew;
	};
	void ArousePassiveSpellEvent(const SpellPrototype* pSpellProto, uint32 spellLevel);
	void CachePassiveSpellStatus(const SpellPrototype* pSpellProto, uint32 spellLevel);
	void ResetClusterPassiveSpellStatus(SpellPassiveBy spellPassiveBy);
	void ApplyClusterPassiveSpellStatus(SpellPassiveBy spellPassiveBy);
	std::unordered_map<uint32, PassiveSpellInfo>
		m_allPassiveSpellInfos[(int)SpellPassiveMode::Count]
		[MAX((int)SpellPassiveBy::StatusMax, (int)SpellPassiveBy::EventMax)];
	std::vector<PassiveSpellStatus>m_passiveSpellStatus[(int)SpellPassiveBy::StatusMax];

public:
	GErrorCode CanGrabFgSpell(
		const SpellPrototype* pSpellProto, uint32 spellLevel = 1);
	GErrorCode CanCastWithoutLearnSpell2Target(
		LocatableObject* pTarget, uint32 spellID, uint32 spellLevel = 1,
		int spellCastFlags = 0, const vector3f& tgtEffPos = vector3f_INVALID,
		const std::string_view& args = emptyStringView);
	GErrorCode CanCastWithoutLearnSpell(uint32 spellID, uint32 spellLevel = 1,
		int spellCastFlags = 0, const vector3f& tgtEffPos = vector3f_INVALID,
		const std::string_view& args = emptyStringView);
	GErrorCode CastWithoutLearnSpell2Target(
		LocatableObject* pTarget, uint32 spellID, uint32 spellLevel = 1,
		uint64 spellInstGuid = 0, const vector3f& tgtEffPos = vector3f_INVALID,
		const std::string_view& args = emptyStringView);
	GErrorCode CastWithoutLearnSpell(uint32 spellID, uint32 spellLevel = 1,
		uint64 spellInstGuid = 0, const vector3f& tgtEffPos = vector3f_INVALID,
		const std::string_view& args = emptyStringView);
	GErrorCode CanCastSpell2Target(LocatableObject* pTarget, uint32 spellID,
		int spellCastFlags = 0, const vector3f& tgtEffPos = vector3f_INVALID,
		const std::string_view& args = emptyStringView);
	GErrorCode CanCastSpell(uint32 spellID, int spellCastFlags = 0,
		const vector3f& tgtEffPos = vector3f_INVALID,
		const std::string_view& args = emptyStringView);
	GErrorCode CastSpell2Target(LocatableObject* pTarget, uint32 spellID,
		uint64 spellInstGuid = 0, const vector3f& tgtEffPos = vector3f_INVALID,
		const std::string_view& args = emptyStringView);
	GErrorCode CastSpell(uint32 spellID, uint64 spellInstGuid = 0,
		const vector3f& tgtEffPos = vector3f_INVALID,
		const std::string_view& args = emptyStringView);
protected:
	GErrorCode CastSpell(
		LocatableObject* pTarget, const SpellPrototype* pSpellProto,
		uint32 spellLevel, uint64 spellInstGuid,
		const vector3f& tgtEffPos, const std::string_view& args);

public:
	void InterruptFgSpell();
	void InterruptSpell(SpellInterruptBy interruptType, bool isAll = true);
	void InterruptSpellById(uint32 spellID, bool isAll = true);
	void InterruptSpellByGuid(uint64 spellInstGuid, bool isAll = true);
	void InterruptSpellBuffByType(uint32 spellEffectType);
	void InterruptSpellBuffByStyle(uint32 spellEffectStyle);

public:
	void OnCastSpell(const SpellPrototype* pSpellProto, uint32 spellLevel);
	void OnStartSpell(Spell* pSpell);
	void OnStopSpell(Spell* pSpell);
	void OnReleaseSpell(Spell* pSpell);
	bool IsSpellCasting() const { return m_fgSpell != NULL; }
	Spell* GetSpellCasting() const { return m_fgSpell; }
protected:
	void DestructAllSpells();
	void CleanSpells();
	Spell* m_fgSpell;
	std::vector<Spell*> m_bgSpells;
	std::vector<Spell*> m_stopSpells;
	std::vector<Spell*> m_zombieSpells;

public:
	void SpellBuffEvent_OnHurt(Unit* pVictim, uint64 hurtValue);
	void SpellBuffEvent_OnHurted(Unit* pHurter, uint64 hurtValue);
	void SpellBuffEvent_OnKill(Unit* pVictim);
	void SpellBuffEvent_OnKilled(Unit* pKiller);
public:
	void AttachSpellBuffInfo(LuaTable&& t);
	void DetachSpellBuffInfo(uint32 key);
	void InterruptSpellBuffInfo(uint32 key);
	uint32 NewSpellBuffKey() { return ++m_spellBuffUniqueKey; }
protected:
	void SendSpellBuffInfo(uint32 key,
		const SpellBuffInfo* pSpellBuffInfo, bool isAttach = true);
	void DestructAllSpellBuffInfos();
	void CleanSpellBuffInfos();
	uint32 m_spellBuffUniqueKey;
	std::map<uint32, SpellBuffInfo*> m_spellBuffInfos;
	std::vector<uint32> m_gcSpellBuffKeys;

public:
	GErrorCode Cooldown_CanCast(const SpellPrototype* pSpellProto) const;
	GErrorCode Cooldown_CanCast_Item(const ItemPrototype *pItemProto) const;
	void Cooldown_Add(const SpellPrototype* pSpellProto, uint32 spellLevel);
	void Cooldown_Add_Item(const ItemPrototype *pItemProto);
	void Cooldown_Reset(const SpellPrototype* pSpellProto);
	void Cooldown_Reset_Item(const ItemPrototype *pItemProto);
protected:
	GErrorCode _Cooldown_CanCast_Raw(CooldownType Type, uint32 Key) const;
	void _Cooldown_Add_Raw(const SpellPrototype* pSpellProto,
		CooldownType Type, uint32 Key, uint64 CooldownRemain, uint64 CooldownMax);
	void _Cooldown_Remove(CooldownType Type, uint32 Key);
	void SendSyncCooldownPacket(
		const SpellPrototype* pSpellProto, const INetPacket& pck);
	std::unordered_map<uint32, SpellCooldownInfo> m_cooldownMap[COOLDOWN_TYPE_COUNT];

public:
	void AddReferAuraObject(AuraObject* pAuraObject);
	void RemoveReferAuraObject(AuraObject* pAuraObject);
protected:
	void MoveAllReferAuraObjects();
	void InterruptAllReferAuraObjects();
	void InterruptAllCastAuraObjects(uint32 spellID);
	std::unordered_set<AuraObject*> m_referAuraObjects;

private:
	void ObjectHookEvent_OnUnitChangeCombatStatus();
	void ObjectHookEvent_OnUnitHurted(Unit* pHurter, uint64 hurtValue);
	void ObjectHookEvent_OnUnitKilled(Unit* pKiller);
	void ObjectHookEvent_OnUnitDead(Unit* pKiller);
};

inline double Unit::GetHPRate() const {
	return double(GetS64Value(UNIT64_FIELD_HP)) / std::max(GetS64Value(UNIT64_FIELD_HP_MAX), 1ll);
}
inline double Unit::GetMPRate() const {
	return double(GetS64Value(UNIT64_FIELD_MP)) / std::max(GetS64Value(UNIT64_FIELD_MP_MAX), 1ll);
}
